/**
 * Copyright (C), 2020, JieYi Software System Co., Ltd.
 * All rights reserved.
 * FileName:       WebSSHService.java
 *
 * @Description:
 * @author: feiwe
 * Modification History:
 * Date         Author        Version        Discription
 * -------------------------------------------------------
 * 2020/12/2      feiwe                         add
 */


package com.fengwei.service;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fengwei.bean.SSHConnectInfo;
import com.fengwei.bean.WebSSHData;
import com.fengwei.config.CommonProperty;
import com.fengwei.dao.MdmsSshserverMapper;
import com.fengwei.dao.MdmsSshserverUserMapper;
import com.fengwei.dao.MdmsUserSshserverMapper;
import com.fengwei.form.MdmsSshserver;
import com.fengwei.form.MdmsSshserverUser;
import com.fengwei.form.MdmsUser;
import com.fengwei.form.MdmsUserSshserver;
import com.fengwei.util.HexStringUtil;
import com.fengwei.util.MdmsConsts;
import com.fengwei.util.PltResult;
import com.fengwei.util.VerifyException;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Service
public class WebSSHService {
    private final static Logger logger = LoggerFactory.getLogger(WebSSHService.class);

    @Autowired
    private CommonProperty commonProperty;

    @Autowired
    private MdmsSshserverMapper mdmsSshserverMapper;

    @Autowired
    private MdmsSshserverUserMapper mdmsSshserverUserMapper;

    @Autowired
    private MdmsUserSshserverMapper mdmsUserSshserverMapper;

    //存放ssh连接信息的map
    private static Map<String, Object> sshMap = new ConcurrentHashMap<>();

    //线程池
    private ExecutorService executorService = Executors.newCachedThreadPool();


    /**
     * @Description: 初始化连接
     * @Param: [session]
     * @return: void
     * @Author: NoCortY
     * @Date: 2020/3/7
     */
    public void initConnection(WebSocketSession session) {
        JSch jSch = new JSch();
        SSHConnectInfo sshConnectInfo = new SSHConnectInfo();
        sshConnectInfo.setjSch(jSch);
        sshConnectInfo.setWebSocketSession(session);
        String uuid = String.valueOf(session.getAttributes().get(MdmsConsts.USER_UUID_KEY));
        //将这个ssh连接信息放入map中
        sshMap.put(uuid, sshConnectInfo);
    }

    /**
     * @Description: 处理客户端发送的数据
     * @Param: [buffer, session]
     * @return: void
     * @Author: NoCortY
     * @Date: 2020/3/7
     */
    public void recvHandle(String buffer, WebSocketSession session) {
        ObjectMapper objectMapper = new ObjectMapper();
        WebSSHData webSSHData = null;
        try {
            webSSHData = objectMapper.readValue(buffer, WebSSHData.class);
        } catch (IOException e) {
            logger.error("Json转换异常");
            logger.error("异常信息:{}", e.getMessage());
            return;
        }
        String userId = String.valueOf(session.getAttributes().get(MdmsConsts.USER_UUID_KEY));
        if (MdmsConsts.WEBSSH_OPERATE_CONNECT.equals(webSSHData.getOperate())) {
            //找到刚才存储的ssh连接对象
            SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
            //启动线程异步处理
            WebSSHData finalWebSSHData = webSSHData;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        connectToSSH(sshConnectInfo, finalWebSSHData, session);
                    } catch (JSchException | IOException | VerifyException e) {
                        String returnMsg = "";
                        if (e instanceof VerifyException) {
                            returnMsg = ((VerifyException) e).getErrCd() + ":" + ((VerifyException) e).getErrorDesc();
                        } else {
                            logger.error("webssh连接异常，" + e.getMessage(), e);
                            returnMsg = e.getMessage();
                        }
                        try {
                            session.sendMessage(new TextMessage(returnMsg));
                        } catch (IOException ioException) {
                            logger.error("发送到websession异常，" + ioException.getMessage(), ioException);
                        }
                        close(session);
                    }
                }
            });
        } else if (MdmsConsts.WEBSSH_OPERATE_COMMAND.equals(webSSHData.getOperate())) {
            String command = webSSHData.getCommand();
            SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
            if (sshConnectInfo != null) {
                try {
                    transToSSH(sshConnectInfo.getChannel(), command);
                } catch (IOException e) {
                    logger.error("webssh连接异常");
                    logger.error("异常信息:{}", e.getMessage());
                    close(session);
                }
            }
        } else {
            logger.error("不支持的操作");
            close(session);
        }
    }

    public void sendMessage(WebSocketSession session, byte[] buffer) throws IOException {
        session.sendMessage(new TextMessage(buffer));
    }

    public void close(WebSocketSession session) {
        String userId = String.valueOf(session.getAttributes().get(MdmsConsts.USER_UUID_KEY));
        SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
        if (sshConnectInfo != null) {
            //断开连接
            if (sshConnectInfo.getChannel() != null) {
                sshConnectInfo.getChannel().disconnect();
            }

            //map中移除
            sshMap.remove(userId);
        }
    }

    /**
     * @Description: 使用jsch连接终端
     * @Param: [cloudSSH, webSSHData, webSocketSession]
     * @return: void
     * @Author: NoCortY
     * @Date: 2020/3/7
     */
    private void connectToSSH(SSHConnectInfo sshConnectInfo, WebSSHData webSSHData, WebSocketSession webSocketSession) throws JSchException,
            IOException, VerifyException {
        Session session = null;
        Properties config = new Properties();
        config.put("StrictHostKeyChecking", "no");
        //根据id查询ip、端口、用户名、密码
        //TODO:
        /*解密*/
        String sshServerUserKey = (String) commonProperty.getSshServerUserKeyMap().get("key" + webSSHData.getServerId());
        MdmsUser mdmsUser = (MdmsUser) commonProperty.getSshServerUserKeyMap().get("user" + webSSHData.getServerId());
        SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, HexStringUtil.hexStringToBytes(sshServerUserKey));
        String serverUserId = aes.decryptStr(webSSHData.getServerId(), CharsetUtil.CHARSET_UTF_8);
        logger.debug("serverUserId:" + serverUserId);
        commonProperty.getSshServerUserKeyMap().remove("key" + webSSHData.getServerId());
        commonProperty.getSshServerUserKeyMap().remove("user" + webSSHData.getServerId());

        /*获取服务器信息和校验权限*/
        Map mapSelectSshserverUser = new HashMap();
        mapSelectSshserverUser.put("id", Long.parseLong(serverUserId));
        MdmsSshserverUser mdmsSshserverUser = mdmsSshserverUserMapper.selectByPrimaryKey(mapSelectSshserverUser);
        if (null == mdmsSshserverUser) {
            logger.error("未找到ssh服务器对应的用户");
            throw new VerifyException(PltResult.RESULT_CF316);
        }
        Map mapSelectSshserver = new HashMap();
        mapSelectSshserver.put("id", mdmsSshserverUser.getServerId());
        MdmsSshserver mdmsSshserver = mdmsSshserverMapper.selectByPrimaryKey(mapSelectSshserver);
        if (null == mdmsSshserver) {
            logger.error("未找到ssh服务器");
            throw new VerifyException(PltResult.RESULT_CF317);
        }
        if (!MdmsConsts.USER_TYPE_99.equals(mdmsUser.getUserType())) {
            Map mapSelectUserSshserver = new HashMap();
            mapSelectUserSshserver.put("userId", mdmsUser.getId());
            mapSelectUserSshserver.put("serverUserId", mdmsSshserverUser.getId());
            MdmsUserSshserver mdmsUserSshserver = mdmsUserSshserverMapper.selectByUKey(mapSelectUserSshserver);
            if (null == mdmsUserSshserver) {
                logger.error("用户无该ssh服务器访问权限");
                throw new VerifyException(PltResult.RESULT_CF318);
            }
        }

        //获取jsch的会话
        session = sshConnectInfo.getjSch().getSession(mdmsSshserverUser.getServerUser(), mdmsSshserver.getServerHost(), mdmsSshserver.getServerPort());
        session.setConfig(config);
        //设置密码
        SymmetricCrypto aesForServerPassword = new SymmetricCrypto(SymmetricAlgorithm.AES,
                HexStringUtil.hexStringToBytes(commonProperty.getSshserverAesKey()));
        String serverPassword = aesForServerPassword.decryptStr(mdmsSshserverUser.getServerPassword(), CharsetUtil.CHARSET_UTF_8);
        session.setPassword(serverPassword);
        //连接  超时时间30s
        session.connect(30000);

        //开启shell通道
        Channel channel = session.openChannel("shell");

        //通道连接 超时时间3s
        channel.connect(3000);

        //设置channel
        sshConnectInfo.setChannel(channel);

        //转发消息
        transToSSH(channel, "\r");

        //读取终端返回的信息流
        InputStream inputStream = channel.getInputStream();
        try {
            //循环读取
            byte[] buffer = new byte[1024];
            int i = 0;
            //如果没有数据来，线程会一直阻塞在这个地方等待数据。
            while ((i = inputStream.read(buffer)) != -1) {
                sendMessage(webSocketSession, Arrays.copyOfRange(buffer, 0, i));
            }

        } finally {
            //断开连接后关闭会话
            session.disconnect();
            channel.disconnect();
            if (inputStream != null) {
                inputStream.close();
            }
        }

    }

    /**
     * @Description: 将消息转发到终端
     * @Param: [channel, data]
     * @return: void
     * @Author: NoCortY
     * @Date: 2020/3/7
     */
    private void transToSSH(Channel channel, String command) throws IOException {
        if (channel != null) {
            OutputStream outputStream = channel.getOutputStream();
            outputStream.write(command.getBytes());
            outputStream.flush();
        }
    }

}
